﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace agree
{
	using schema;

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// virtual property store allows us to attach arbitrary objects to any other in an ad-hoc fashion, with efficient
	/// sparse storage in mind. and also without holding external strong references
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class VirtualPropertyStore
	{
		public static readonly VirtualPropertyStore vps;

		static VirtualPropertyStore()
		{
			VirtualPropertyStore.vps = new VirtualPropertyStore();
		}

		public VirtualPropertyStore()
		{
			this.cwt = new ConditionalWeakTable<ISchemaObj, MemberStore>();
		}
		readonly ConditionalWeakTable<ISchemaObj, MemberStore> cwt;

		public int GetPropertyCount(ISchemaObj item)
		{
			MemberStore iaps;
			return cwt.TryGetValue(item, out iaps) ? iaps.PropertyCount : 0;
		}

		public void SetPropertyValue(ISchemaObj item, schema_prop_attachable prop, Object value)
		{
			Debug.Assert(item != null);
			MemberStore iaps;
			if (!cwt.TryGetValue(item, out iaps))
			{
				iaps = new MemberStore();
				try
				{
					cwt.Add(item, iaps);
				}
				catch (ArgumentException)
				{
					if (!cwt.TryGetValue(item, out iaps))
						throw new InvalidOperationException();
				}
			}
			iaps.SetProperty(prop, value);
		}

		public bool TryGetPropertyValue<T>(ISchemaObj item, schema_prop_attachable prop, out T value)
		{
			MemberStore iaps;
			if (!cwt.TryGetValue(item, out iaps))
			{
				value = default(T);
				return false;
			}
			Object tt;
			bool b;
			lock (iaps)
				b = iaps.TryGetValue(prop, out tt);
			value = b ? (T)tt : default(T);
			return b;
		}
		public Object GetPropertyValue(ISchemaObj item, schema_prop_attachable prop)
		{
			MemberStore iaps;
			if (!cwt.TryGetValue(item, out iaps))
				return null;
			Object tt;
			lock (iaps)
				iaps.TryGetValue(prop, out tt);
			return tt;
		}

		public bool RemovePropertyValue(ISchemaObj item, schema_prop_attachable prop)
		{
			MemberStore iaps;
			return cwt.TryGetValue(item, out iaps) && iaps.RemoveProperty(prop);
		}

		public void CopyPropertiesTo(ISchemaObj obj, KeyValuePair<schema_prop_attachable, Object>[] array, int index)
		{
			MemberStore iaps;
			if (cwt.TryGetValue(obj, out iaps))
				iaps.CopyPropertiesTo(array, index);
		}


		public sealed class MemberStore : Dictionary<schema_prop_attachable, Object>
		{
			public int PropertyCount { get { lock (this) return base.Count; } }

			public schema_prop_attachable[] Properties
			{
				get
				{
					schema_prop_attachable[] ret;
					int i = 0;
					lock (this)
					{
						ret = new schema_prop_attachable[base.Count];
						foreach (var p in this.Keys)
							ret[i++] = p;
						i++;
					}
					return ret;
				}
			}

			public void CopyPropertiesTo(KeyValuePair<schema_prop_attachable, Object>[] array, int index)
			{
				lock (this)
				{
					int c = base.Count;
					foreach (var kvp in this)
						if (index < c)
							array[index++] = new KeyValuePair<schema_prop_attachable, Object>(kvp.Key, kvp.Value);
				}
			}

			public void SetProperty(schema_prop_attachable prop, Object value)
			{
				if (value == null)
				{
					RemoveProperty(prop);
					return;
				}
				lock (this)
					this[prop] = value;
			}

			public bool RemoveProperty(schema_prop_attachable prop)
			{
				bool b;
				lock (this)
					b = base.Remove(prop);
				return b;
			}
		};
	};
}
